﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Tycho.Runtime;
using Tycho.Parser.Tokens;
using Tycho.Parser;
using System.IO;

namespace Tycho.Compiler {
    public abstract class SymbolScope {
        protected const int SymbolNotFound = -1;

        public int GetFrameIndex (AnyObject name, AnyObject term) {
            int index = FindFrameIndex (name, 0);

            if (index != SymbolNotFound) {
                return index;
            } else {
                throw new VariableNotDeclaredCompilationException (name, term);
            }
        }

        public abstract int FindFrameIndex (AnyObject name, int index);

        public abstract string PrettyPrint ();
    }

    public class SimpleSymbolScope : SymbolScope {
        SymbolScope Parent;
        HashSet<AnyObject> Symbols;

        public SimpleSymbolScope (SymbolScope parent) {
            Parent = parent;
            Symbols = new HashSet<AnyObject> ();
        }

        public override int FindFrameIndex (AnyObject name, int index) {
            if (Symbols.Contains (name)) {
                return index;
            } else if (Parent != null) {
                return Parent.FindFrameIndex (name, index + 1);
            } else {
                return SymbolNotFound;
            }
        }

        public override string PrettyPrint () {
            var output = new StringWriter ();

            if (Parent != null) {
                output.Write(Parent.PrettyPrint ());
                output.WriteLine ();
            }

            foreach (var sym in Symbols) {
                output.WriteLine (sym + ", " + sym.GetHashCode ());
            }

            return output.ToString ();
        }

        public int DeclareVariableTentative (AnyObject name) {
            int frameIndex = FindFrameIndex (name, 0);

            if (frameIndex == SymbolNotFound) {
                Symbols.Add (name);
                return 0;
            } else {
                // variable already declared, return it's existing frame index
                return frameIndex;
            }
        }

        public int DeclareVariable (AnyObject name, AnyObject term) {
            return DeclareVariable (name, term.SourceLocation);
        }

        public int DeclareVariable (AnyObject name, SourceLocation sloc) {
            int frameIndex = FindFrameIndex (name, 0);

            if (frameIndex == SymbolNotFound) {
                Symbols.Add (name);
                return 0;
            } else {
                throw new CompilationException (String.Format ("variable `{0}' already declared", name), sloc);
            }
        }

        public int DeclareVariableOverride (AnyObject name, SourceLocation sloc) {
            int frameIndex = FindFrameIndex (name, 0);

            // we redeclare the variable if its in an outer frame but not the current one (the zeroeth one).
            if (frameIndex != 0) {
                Symbols.Add (name);
                return 0;
            } else {
                throw new CompilationException (String.Format ("variable `{0}' already declared in frame", name), sloc);
            }
        }
    }
}
